`timescale 1ns / 1ps

`include "MIPSCPU_COMMON.vh"

module Execute(
	ex_enable,
	op_type,
	op_subtype,
	value1,
	value2,
	value3,
	current_pc_value,
	jump_reg,
	reg_write_enable,
	reg_write_addr,
	reg_write_hi_enable,
	reg_write_lo_enable,
	ex_out_enable,
	out_jump_enable,
	out_new_pc_value,
	out_reg_write_enable,
	out_reg_write_addr,
	out_reg_written_value,
	out_reg_write_hi_enable,
	out_reg_write_lo_enable
    );
	
	input ex_enable;
	input[`OPERATION_TYPE_WIDTH - 1 : 0] op_type;
	input[`OPERATION_SUBTYPE_WIDTH - 1 : 0] op_subtype;
	input[`DATA_WIDTH - 1 : 0] value1, value2, value3, current_pc_value;
	input reg_write_enable, reg_write_hi_enable, reg_write_lo_enable;
	input[`REGISTER_ADDRESS_WIDTH - 1 : 0] reg_write_addr;
	input jump_reg;
	output reg ex_out_enable, out_jump_enable;
	output reg[`DATA_WIDTH - 1 : 0] out_new_pc_value;
	output reg out_reg_write_enable, out_reg_write_hi_enable, out_reg_write_lo_enable;
	output reg[`REGISTER_ADDRESS_WIDTH - 1 : 0] out_reg_write_addr;
	output reg[`DATA_WIDTH - 1 : 0] out_reg_written_value;
	
	wire[`DATA_WIDTH - 1 : 0] rpc_value, alu_result;
	assign rpc_value = current_pc_value + `INSTRUCTION_BYTES;
	
	ALU alu_i
	(
		.op_subtype(op_subtype),
		.value1(value1),
		.value2(value2),
		.out_result(alu_result)
	);
	
	always @(*)
	begin
		ex_out_enable <= ex_enable;
		if (ex_enable) begin
			out_reg_write_enable <= reg_write_enable;
			out_reg_write_hi_enable <= reg_write_hi_enable;
			out_reg_write_lo_enable <= reg_write_lo_enable;
			out_reg_write_addr <= reg_write_addr;
			if (op_type == `OPERATION_TYPE_ALU) begin
				out_reg_written_value <= alu_result;
				out_jump_enable <= 0;
				out_new_pc_value <= current_pc_value;
			end else if (op_type == `OPERATION_TYPE_BRANCH) begin
				if (alu_result[0]) begin
					out_jump_enable <= 1;
					out_new_pc_value <= current_pc_value + value3;
				end else begin
					out_jump_enable <= 0;
					out_new_pc_value <= current_pc_value;
				end
				if (reg_write_enable)
					out_reg_written_value <= rpc_value + `INSTRUCTION_BYTES;
				else
					out_reg_written_value <= 0;
			end else if (op_type == `OPERATION_TYPE_JUMP) begin
				out_jump_enable <= 1;
				if (jump_reg)
					out_new_pc_value <= value3;
				else
					out_new_pc_value <= 
						{rpc_value[`INSTRUCTION_J_ADDR_PC_EXT_BEGIN : `INSTRUCTION_J_ADDR_PC_EXT_END],
						 value3[`INSTRUCTION_J_ADDR_PC_EXT_END - 1 : 0]};
				if (reg_write_enable)
					out_reg_written_value <= rpc_value + `INSTRUCTION_BYTES;
				else
					out_reg_written_value <= 0;
			end else begin
				out_jump_enable <= 0;
				out_new_pc_value <= current_pc_value;
				out_reg_written_value <= 0;
			end
		end else begin
			out_reg_write_enable <= 0;
			out_reg_write_hi_enable <= 0;
			out_reg_write_lo_enable <= 0;
			out_reg_write_addr <= `MIPS_CPU_REGISTER_ZERO_NO;
			out_reg_written_value <= 0;
			out_jump_enable <= 0;
			out_new_pc_value <= current_pc_value;
		end
	end
	
endmodule
